package com.gitee.luosl.cola.match.pattern;

import com.gitee.luosl.cola.lang.Tuple;
import com.gitee.luosl.cola.lang.Tuple2;
import com.gitee.luosl.cola.lang.Tuple3;
import com.gitee.luosl.cola.lang.Tuple4;
import com.gitee.luosl.cola.match.Pr;
import com.gitee.luosl.cola.util.CollUtils;
import com.gitee.luosl.cola.util.Try;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;

public class Pa {

    private static Pattern<Object, Object> ANY = new Pattern<Object, Object>() {
        @Override
        public Object apply(Object value) {
            return value;
        }

        @Override
        public boolean isDefinedAt(Object value) {
            return true;
        }
    };

    @SuppressWarnings("unchecked")
    public static <T> Pattern<T, T> p() {
        return (Pattern<T, T>) ANY;
    }

    public static <T> Pattern<T, T> p(Predicate<T> predicate) {
        return Pattern.create(Function.identity(), predicate);
    }

    public static <T> Pattern<T, T> pNull(){
        return p(Pr.isNull());
    }

    public static <T> Pattern<T, T> pNotNull(){
        return p(Pr.isNotNull());
    }

    public static <T, U> Pattern<T, T> pPeek(Function<T, U> mapper, Pattern<U, ?> pattern){
        return Pattern.create(Function.identity(), (T value) -> pattern.isDefinedAt(mapper.apply(value)));
    }

    public static <T, U1, U2> Pattern<T, T> pPeek(Function<T, U1> mapper1, Function<T, U2> mapper2, Pattern<U1, ?> pattern1, Pattern<U2, ?> pattern2){
        return Pattern.create(Function.identity(), (T value) -> pattern1.isDefinedAt(mapper1.apply(value)) && pattern2.isDefinedAt(mapper2.apply(value)));
    }

    public static <T, U1, U2, U3> Pattern<T, T> pPeek(Function<T, U1> mapper1,
                                                      Function<T, U2> mapper2,
                                                      Function<T, U3> mapper3,
                                                      Pattern<U1, ?> pattern1,
                                                      Pattern<U2, ?> pattern2,
                                                      Pattern<U3, ?> pattern3){
        return Pattern.create(Function.identity(), (T value) -> pattern1.isDefinedAt(mapper1.apply(value))
                && pattern2.isDefinedAt(mapper2.apply(value))
                && pattern3.isDefinedAt(mapper3.apply(value))
        );
    }

    public static <T, U1, U2, U3, U4> Pattern<T, T> pPeek(Function<T, U1> mapper1,
                                                          Function<T, U2> mapper2,
                                                          Function<T, U3> mapper3,
                                                          Function<T, U4> mapper4,
                                                          Pattern<U1, ?> pattern1,
                                                          Pattern<U2, ?> pattern2,
                                                          Pattern<U3, ?> pattern3,
                                                          Pattern<U4, ?> pattern4){
        return Pattern.create(Function.identity(), (T value) -> pattern1.isDefinedAt(mapper1.apply(value))
                && pattern2.isDefinedAt(mapper2.apply(value))
                && pattern3.isDefinedAt(mapper3.apply(value))
                && pattern4.isDefinedAt(mapper4.apply(value))
        );
    }

    public static <T> Pattern1<T, T> p(Class<T> clazz) {
        return Pattern1.of(
                clazz,
                Pr.awaysTrue(),
                p(),
                Function.identity()
        );
    }

    public static <T> Pattern1<T, T> p(T value){
        return Pattern1.create(Pr.eq(value));
    }

    public static <T> Pattern1<T, T> pIn(T ...values){
        return Pattern1.create(Pr.isIn(values));
    }

    public static <T> Pattern1<T, T> pNotIn(T ...values){
        return Pattern1.create(Pr.isNotIn(values));
    }

    public static <T, R> Pattern1<Optional<T>, R> pSome(Pattern<T, R> pattern) {
        return Pattern1.of(
                Optional.class,
                Optional::isPresent,
                pattern,
                Optional::get
        );
    }

    public static <T> Pattern1<Optional<T>, T> pSome(T value) {
        return pSome(p(value));
    }

    public static <T> Pattern<Optional<T>, T> pSome(){
        return pSome(p());
    }

    public static <T> Pattern<Optional<T>, Void> pNone(){
        return Pattern1.of(
                Optional.class,
                opt -> !opt.isPresent(),
                p(),
                ignore -> null
        );
    }

    public static <T, R> Pattern1<Try<T>, R> pSuccess(Pattern<T, R> pattern){
        return Pattern1.of(
                Try.class,
                Try::isSuccess,
                pattern,
                Try::get
        );
    }

    public static <T> Pattern1<Try<T>, T> pSuccess(T value) {
        return pSuccess(p(value));
    }

    @SuppressWarnings("unchecked")
    public static <T, E extends Throwable, R> Pattern1<Try<T>, R> pFailure(Pattern<E, R> pattern) {
        return Pattern1.of(
                Try.class,
                Try::isFailure,
                pattern,
                t -> (E) t.getCause()
        );
    }

    public static <T, E extends Throwable> Pattern1<Try<T>, E> pFailure(Class<E> clazz) {
        return pFailure(p(clazz));
    }

    public static <T> Pattern<Try<T>, Void> pFailure() {
        return pFailure(Pattern.create(ignore -> null, ignore -> true));
    }

    public static <T1, T2, R1, R2> Pattern2<Tuple2<T1, T2>, R1, R2> pTuple(Pattern<T1, R1> pattern1, Pattern<T2, R2> pattern2){
        return Pattern2.of(
                Tuple2.class,
                Pr.awaysTrue(),
                pattern1,
                pattern2,
                Function.identity()
        );
    }

    public static <T1, T2, T3, R1, R2, R3> Pattern3<Tuple3<T1, T2, T3>, R1, R2, R3> pTuple(Pattern<T1, R1> pattern1, Pattern<T2, R2> pattern2, Pattern<T3, R3> pattern3){
        return Pattern3.of(
                Tuple3.class,
                Pr.awaysTrue(),
                pattern1,
                pattern2,
                pattern3,
                Function.identity()
        );
    }

    public static <T1, T2, T3, T4, R1, R2, R3, R4> Pattern4<Tuple4<T1, T2, T3, T4>, R1, R2, R3, R4> pTuple(Pattern<T1, R1> pattern1, Pattern<T2, R2> pattern2, Pattern<T3, R3> pattern3, Pattern<T4, R4> pattern4){
        return Pattern4.of(
                Tuple4.class,
                Pr.awaysTrue(),
                pattern1,
                pattern2,
                pattern3,
                pattern4,
                Function.identity()
        );
    }

    public static <T> ListPattern<T> pNil() {
        return new ListPattern<>(
                Pattern1.of(
                        List.class,
                        list -> list.size() == 0,
                        p(),
                        Function.identity()
                )
        );
    }

    public static <T> ListPattern<T> pList() {
        return new ListPattern<>(
                Pattern1.of(
                        List.class,
                        Pr.awaysTrue(),
                        p(),
                        Function.identity()
                )
        );
    }

    public static <T, R> Pattern1<List<T>, R> pList(Pattern<T, R> pattern) {
        return Pattern1.of(
                List.class,
                list -> list.size() == 1,
                pattern,
                list -> list.get(0)
        );
    }

    public static <T, R1, R2> Pattern2<List<T>, R1, R2> pList(Pattern<T, R1> p1, Pattern<T, R2> p2) {
        return Pattern2.of(
                List.class,
                list -> list.size() == 2,
                p1, p2,
                list -> Tuple.of(list.get(0), list.get(1))
        );
    }

    public static <T, R1, R2> Pattern2<List<T>, R1, List<T>> pList(Pattern<T, R1> p1, ListPattern<T> p2) {
        return Pattern2.of(
                List.class,
                list -> list.size() >= 1,
                p1, p2,
                list -> Tuple.of(list.get(0), CollUtils.subList(list, 1, list.size()))
        );
    }

    public static <T, R1, R2, R3> Pattern3<List<T>, R1, R2, R3> pList(Pattern<T, R1> p1, Pattern<T, R2> p2, Pattern<T, R3> p3) {
        return Pattern3.of(
                List.class,
                list -> list.size() == 3,
                p1, p2, p3,
                list -> Tuple.of(list.get(0), list.get(1), list.get(2))
        );
    }

    public static <T, R1, R2> Pattern3<List<T>, R1, R2, List<T>> pList(Pattern<T, R1> p1, Pattern<T, R2> p2, ListPattern<T> p3) {
        return Pattern3.of(
                List.class,
                list -> list.size() >= 2,
                p1, p2, p3,
                list -> Tuple.of(list.get(0), list.get(1), CollUtils.subList(list, 2, list.size()))
        );
    }

    public static <T, R1, R2, R3, R4> Pattern4<List<T>, R1, R2, R3, R4> pList(Pattern<T, R1> p1, Pattern<T, R2> p2, Pattern<T, R3> p3, Pattern<T, R4> p4) {
        return Pattern4.of(
                List.class,
                list -> list.size() == 4,
                p1, p2, p3, p4,
                list -> Tuple.of(list.get(0), list.get(1), list.get(2), list.get(3))
        );
    }

    public static <T, R1, R2, R3> Pattern4<List<T>, R1, R2, R3, List<T>> pList(Pattern<T, R1> p1, Pattern<T, R2> p2, Pattern<T, R3> p3, ListPattern<T> p4) {
        return Pattern4.of(
                List.class,
                list -> list.size() >= 3,
                p1, p2, p3, p4,
                list -> Tuple.of(list.get(0), list.get(1), list.get(2), CollUtils.subList(list, 3, list.size()))
        );
    }

    public static RegexPattern regex(java.util.regex.Pattern regexPattern){
        return new RegexPattern(regexPattern);
    }

    public static RegexPattern regex(String regex){
        return new RegexPattern(java.util.regex.Pattern.compile(regex));
    }

    public static <T, U, R> Pattern1<T, R> pUnapply(Function<T, U> unapply, Pattern<U, R> pattern) {
        return new Pattern1<T, R>() {
            @Override
            public R apply(T value) {
                return pattern.apply(unapply.apply(value));
            }

            @Override
            public boolean isDefinedAt(T value) {
                return pattern.isDefinedAt(unapply.apply(value));
            }
        };
    }

    public static <T, U1, U2, R1, R2> Pattern2<T, R1, R2> pUnapply(Function<T, U1> unapply1,
                                                                   Function<T, U2> unapply2,
                                                                   Pattern<U1, R1> pattern1,
                                                                   Pattern<U2, R2> pattern2){
        return new Pattern2<T, R1, R2>() {
            @Override
            public Tuple2<R1, R2> apply(T value) {
                return Tuple.of(
                        pattern1.apply(unapply1.apply(value)),
                        pattern2.apply(unapply2.apply(value))
                );
            }

            @Override
            public boolean isDefinedAt(T value) {
                return pattern1.isDefinedAt(unapply1.apply(value)) && pattern2.isDefinedAt(unapply2.apply(value));
            }
        };

    }

    public static <T, U1, U2, U3, R1, R2, R3> Pattern3<T, R1, R2, R3> pUnapply(Function<T, U1> unapply1,
                                                                               Function<T, U2> unapply2,
                                                                               Function<T, U3> unapply3,
                                                                               Pattern<U1, R1> pattern1,
                                                                               Pattern<U2, R2> pattern2,
                                                                               Pattern<U3, R3> pattern3){
        return new Pattern3<T, R1, R2, R3>() {
            @Override
            public Tuple3<R1, R2, R3> apply(T value) {
                return Tuple.of(
                        pattern1.apply(unapply1.apply(value)),
                        pattern2.apply(unapply2.apply(value)),
                        pattern3.apply(unapply3.apply(value))
                );
            }

            @Override
            public boolean isDefinedAt(T value) {
                return pattern1.isDefinedAt(unapply1.apply(value))
                        && pattern2.isDefinedAt(unapply2.apply(value))
                        && pattern3.isDefinedAt(unapply3.apply(value));
            }
        };
    }

    public static <T, U1, U2, U3, U4, R1, R2, R3, R4> Pattern4<T, R1, R2, R3, R4> pUnapply(Function<T, U1> unapply1,
                                                                                           Function<T, U2> unapply2,
                                                                                           Function<T, U3> unapply3,
                                                                                           Function<T, U4> unapply4,
                                                                                           Pattern<U1, R1> pattern1,
                                                                                           Pattern<U2, R2> pattern2,
                                                                                           Pattern<U3, R3> pattern3,
                                                                                           Pattern<U4, R4> pattern4) {
        return new Pattern4<T, R1, R2, R3, R4>() {
            @Override
            public Tuple4<R1, R2, R3, R4> apply(T value) {
                return Tuple.of(
                        pattern1.apply(unapply1.apply(value)),
                        pattern2.apply(unapply2.apply(value)),
                        pattern3.apply(unapply3.apply(value)),
                        pattern4.apply(unapply4.apply(value))
                );
            }

            @Override
            public boolean isDefinedAt(T value) {
                return pattern1.isDefinedAt(unapply1.apply(value))
                        && pattern2.isDefinedAt(unapply2.apply(value))
                        && pattern3.isDefinedAt(unapply3.apply(value))
                        && pattern4.isDefinedAt(unapply4.apply(value));
            }
        };
    }

}
